package com.pingan.haofang.searchcloud.solr.api;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.pingan.haofang.searchcloud.api.exception.SearchException;
import com.pingan.haofang.searchcloud.api.matedata.FieldMeta;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.Condition;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.ConditionGroup;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.GroupBySupport;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.Operator;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.OrderBySupport;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.SqlNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.ColumnNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.aggregate.AggregateFunction;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.aggregate.Count;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.function.GeoDist;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.function.RangeGroup;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.sqlnode.function.RangeGroupGapNode;
import com.pingan.haofang.searchcloud.api.query.parser.dbobject.util.SqlNodeUtil;
import com.pingan.haofang.searchcloud.api.query.parser.element.QueryBlock;
import com.pingan.haofang.searchcloud.api.query.parser.element.SelectStatement;
import com.pingan.haofang.searchcloud.api.query.parser.exception.SqlParseException;
import com.pingan.haofang.searchcloud.solr.constants.SolrConstant;
import com.pingan.haofang.searchcloud.solr.constants.SolrExpression;
import com.pingan.haofang.searchcloud.solr.expression.SolrFacetNode;
import com.pingan.haofang.searchcloud.solr.expression.SolrFacetType;
import com.pingan.haofang.searchcloud.solr.expression.SolrSortType;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * SOLR查询构建器
 *
 * @author LUYI374
 * @date 2017年6月27日
 * @since 1.0.0
 */
public class SolrQueryBuilder {

    public static final String SOLR_AND = " AND ";
    public static final String SOLR_OR = " OR ";
    public static final String SOLR_NOT = " NOT ";

    /**
     * 执行构建
     *
     * @param statement
     * @return
     */
    public static SolrQueryStatement build(SelectStatement statement) {
        QueryBlock queryBlock = statement.getQueryBlock();

        SolrQuery query = new SolrQuery();
        Multimap<String, String> selectItemMapping = HashMultimap.create();

        // 聚合查询
        if (queryBlock.useDistinct() || queryBlock.useAggregate()) {

            // 分组查询
            Map<String, SqlNode> orderByNodeMap = queryBlock.getOrderByNodeMap();
            Map<String, Object> facetNode = null;
            SolrFacetNode parent = null;
            // boolean oneAggLevel = true;

            if (CollectionUtils.isNotEmpty(queryBlock.getGroupByClause())) {
                int index = 0;

                // 针对独占的分组查询的特殊处理
                if (queryBlock.isExclusiveGroupBy()) {

                    SqlNode groupByNode = queryBlock.getSortedGroupByClause().firstEntry().getValue();
                    // 针对range_group函数的特殊处理
                    if (groupByNode.getClass() == RangeGroup.class) {

                        RangeGroup rangeGroup = (RangeGroup) groupByNode;
                        facetNode = new HashMap<String, Object>(rangeGroup.getGapNodeList().size());

                        if (!orderByNodeMap.isEmpty()) {
                            // nabizhang 你个大坑
                            throw new SearchException("cannot support sort when using range_group");
                        }

                        // 多维度不支持排序及分页
                        // if (rangeGroup.getGapNodeList().size() > 1) {
                        // oneAggLevel = false;
                        // }

                        int gapIndex = 0;
                        for (RangeGroupGapNode gapNode : rangeGroup.getGapNodeList()) {

                            SolrFacetNode current = new SolrFacetNode();
                            current.setType(SolrFacetType.range);
                            current.setField(rangeGroup.getColumn().getColumnName());
                            current.setStart(gapNode.getBegin());
                            current.setEnd(gapNode.getEnd());
                            current.setGap(gapNode.getGap());
                            current.setMissing(true);

                            if (queryBlock.usePage()) {
                                // nabizhang 你个大坑
                                throw new SearchException("paginate is not support when using range_group");
                                // if (!oneAggLevel) {
                                // throw new SearchException(
                                // "paginate is not support when using range_group in multi agg");
                                // }
                                // current.setLimit((long) queryBlock.getLimit().getValue());
                                // current.setOffset((long) queryBlock.getOffset().getValue());
                            } else {
                                current.setLimit(-1L);
                            }

                            // if (orderByNodeMap.containsKey(rangeGroup.getColumn().getColumnName())) {
                            // nabizhang 你个大坑
                            // if (!oneAggLevel) {
                            // throw new SearchException(
                            // "cannot support sort when using range_group in multi agg ");
                            // }
                            // SqlNode sqlSortNode = orderByNodeMap.get(rangeGroup.getColumn().getColumnName());
                            // if (SqlNodeUtil.isColumn(sqlSortNode)) {
                            // ColumnNode sortColumnNode = (ColumnNode) sqlSortNode;
                            // LinkedHashMap<String, SolrSortType> sortNode = current.getSort();
                            // if (sortNode == null) {
                            // sortNode = new LinkedHashMap<String, SolrSortType>(1);
                            // current.setSort(sortNode);
                            // }
                            // sortNode.put("index",
                            // sortColumnNode.isAsc() ? SolrSortType.asc : SolrSortType.desc);
                            // }
                            // }

                            String solrColumn = rangeGroup.getColumn().getColumnName() + "_" + gapIndex;
                            selectItemMapping.put(rangeGroup.getColumn().getColumnName(), solrColumn);

                            facetNode.put(solrColumn, current);
                            gapIndex++;
                            parent = current;
                        }
                    }
                } else {

                    for (SqlNode groupByNode : queryBlock.getSortedGroupByClause().values()) {

                        ColumnNode field = ((GroupBySupport) groupByNode).getColumn();

                        Map<String, Object> currentFacetNode = new HashMap<String, Object>(1);

                        SolrFacetNode current = new SolrFacetNode();
                        currentFacetNode.put(field.getColumnName(), current);

                        if (index == 0) {
                            facetNode = currentFacetNode;

                            // 分页信息
                            if (queryBlock.usePage()) {
                                current.setLimit((long) queryBlock.getLimit().getValue());
                                current.setOffset((long) queryBlock.getOffset().getValue());
                            } else {
                                current.setLimit(-1L);
                            }
                        } else {
                            parent.setFacet(currentFacetNode);
                            current.setLimit(-1L);
                        }

                        current.setField(field.getColumnName());
                        current.setType(SolrFacetType.terms);
                        current.setMissing(true);

                        if (orderByNodeMap.containsKey(field.getColumnName())) {
                            SqlNode sqlSortNode = orderByNodeMap.get(field.getColumnName());
                            if (SqlNodeUtil.isColumn(sqlSortNode)) {
                                ColumnNode sortColumnNode = (ColumnNode) sqlSortNode;
                                LinkedHashMap<String, SolrSortType> sortNode = current.getSort();
                                if (sortNode == null) {
                                    sortNode = new LinkedHashMap<String, SolrSortType>(1);
                                    current.setSort(sortNode);
                                }
                                sortNode.put("index", sortColumnNode.isAsc() ? SolrSortType.asc : SolrSortType.desc);
                            }
                        }

                        parent = current;
                        index++;
                    }
                }

                // 填充聚合函数及对应的排序信息
                if (MapUtils.isNotEmpty(queryBlock.getAggregateClause())) {
                    Map<String, Object> aggLeafFacetNode =
                            new HashMap<String, Object>(queryBlock.getAggregateClause().size());
                    for (int aggIndex : queryBlock.getAggregateClause().keySet()) {
                        AggregateFunction function = queryBlock.getAggregateClause().get(aggIndex);
                        String expression = SolrExpression.getExpression(function);
                        if (expression == null) {
                            throw new SearchException("un supported solr function, " + function.getName());
                        }

                        String key = "count";
                        if (function.getClass() != Count.class) {
                            key = SolrConstant.AGG_PREFIX + aggIndex;
                            aggLeafFacetNode.put(key, expression + "(" + function.getColumn().getColumnName() + ")");
                        }

                        if (orderByNodeMap.containsKey(function.getAlias())) {
                            // 针对多个维度的聚合，不允许根据聚合函数排序
                            if (queryBlock.getGroupByClause().size() > 1) {
                                throw new SearchException("order by agg function can only support one group by column, "
                                        + function.getName());
                            }
                            LinkedHashMap<String, SolrSortType> sortNode = parent.getSort();
                            if (sortNode == null) {
                                sortNode = new LinkedHashMap<String, SolrSortType>(1);
                                parent.setSort(sortNode);
                            }
                            sortNode.put(key, function.isAsc() ? SolrSortType.asc : SolrSortType.desc);
                            parent.setSort(sortNode);
                        }

                    }
                    parent.setFacet(aggLeafFacetNode);
                }

            } else {
                facetNode = new HashMap<String, Object>();
                for (int aggIndex : queryBlock.getAggregateClause().keySet()) {
                    AggregateFunction function = queryBlock.getAggregateClause().get(aggIndex);
                    String expression = SolrExpression.getExpression(function);
                    if (expression == null) {
                        throw new SearchException("un supported solr function, " + function.getName());
                    }
                    if (function.getClass() != Count.class) {
                        String key = SolrConstant.AGG_PREFIX + aggIndex;
                        facetNode.put(key, expression + "(" + function.getColumn().getColumnName() + ")");
                    }
                }
            }

            query.set("json.facet", JSON.toJSONString(facetNode));
            query.setRows(0);
        } else {
            // 常规查询，如果是select *，则不设置查询字段
            if (!queryBlock.isWildcard()) {
                for (String column : queryBlock.getSelectColumnMap().keySet()) {
                    query.addField(column);
                }
            }

            // 分页信息
            if (queryBlock.usePage()) {
                query.setStart(((Long) queryBlock.getOffset().getValue()).intValue());
                query.setRows(((Long) queryBlock.getLimit().getValue()).intValue());
            } else {
                query.setStart(0);
                if (!queryBlock.useAggregate()) {
                    query.setRows(SolrConstant.ROW_QUERY_MAX_SIZE);
                }
            }

            // order by信息
            for (SqlNode orderByNode : queryBlock.getOrderByClause()) {
                OrderBySupport orderBy = (OrderBySupport) orderByNode;
                if (orderByNode.getClass() == GeoDist.class) {
                    query.addSort("score", orderBy.isAsc() ? ORDER.asc : ORDER.desc);
                } else if (SqlNodeUtil.isColumn(orderByNode)) {
                    query.addSort(((ColumnNode) orderByNode).getColumnName(),
                            orderBy.isAsc() ? ORDER.asc : ORDER.desc);
                }
            }
        }

        // 构建where条件
        if (CollectionUtils.isEmpty(queryBlock.getWhereConditionGroupList())) {
            query.setQuery("*:*");
        } else {
            StringBuilder q = new StringBuilder();
            buildQueryCondition(q, queryBlock.getWhereConditionGroupList(), queryBlock.getOrIndexSet(), query);
            query.setQuery(q.toString());
        }

        SolrQueryStatement solrStatement = new SolrQueryStatement();
        solrStatement.setSolrQuery(query);
        solrStatement.setSelectItemMapping(selectItemMapping);
        return solrStatement;
    }

    /**
     * 构建查询条件
     *
     * @param q
     * @param groupList
     * @param parentOrIndexSet
     * @param query
     */
    private static void buildQueryCondition(StringBuilder q, List<ConditionGroup> groupList,
                                            Set<Integer> parentOrIndexSet, SolrQuery query) {
        int index = 0;
        boolean groupFlag = true;
        for (ConditionGroup group : groupList) {
            if (groupFlag) {
                groupFlag = false;
            } else {
                if (!parentOrIndexSet.contains(index)) {
                    q.append(SOLR_AND);
                } else {
                    q.append(SOLR_OR);
                }
            }
            if (!group.isChild()) {
                q.append("(");
                StringBuilder qCondition = new StringBuilder();
                buildQueryCondition(qCondition, group.getChildList(), group.getOrIndexSet(), query);
                q.append(qCondition);
                q.append(")");
            } else {
                boolean flag = true;
                List<Condition> conditionList = group.getConditionList();

                SolrQueryNode geoSolrNode = null;
                for (int i = 0; i < conditionList.size(); i++) {
                    Condition condition = conditionList.get(i);

                    SolrQueryNode solrQueryNode = buildOneQueryCondition(condition, group.getDistance());
                    // GEO表达式必须放在最后，暂时不考虑多个GEO和括号或者OR等异常情况
                    if (solrQueryNode.isGeo()) {
                        geoSolrNode = solrQueryNode;
                        continue;
                    }

                    if (flag) {
                        flag = false;
                    } else {
                        q.append(SOLR_AND);
                    }
                    q.append(solrQueryNode.getSolrExpression());
                }

                if (geoSolrNode != null) {
                    if (conditionList.size() > 1) {
                        q.append(SOLR_AND);
                    }
                    q.append(geoSolrNode.getSolrExpression());
                }
            }
            index++;
        }
    }

    /**
     * 构建单个查询条件
     *
     * @param condition
     * @return
     */
    private static SolrQueryNode buildOneQueryCondition(Condition condition, Double distance) {
        StringBuilder sb = new StringBuilder();
        Operator operator = condition.getOperator();
        SolrQueryNode node = new SolrQueryNode();
        switch (operator) {
            case GT:
                sb.append(condition.getLeft().getColumnName()).append(":{").append(condition.getRightOne().getValue())
                        .append(" TO *}");
                break;
            case GE:
                sb.append(condition.getLeft().getColumnName()).append(":[").append(condition.getRightOne().getValue())
                        .append(" TO *]");
                break;
            case EQ:
                if (condition.getRightList().size() == 1) {
                    boolean geo = buildKeywordCondition(condition.getLeft(), condition.getRightOne().getValue(), false,
                            distance, sb);
                    node.setGeo(geo);
                } else {
                    sb.append(condition.getLeft().getColumnName()).append(":(");

                    for (int i = 0; i < condition.getRightList().size(); i++) {
                        if (i > 0) {
                            sb.append(SOLR_OR);
                        }
                        boolean geo = buildKeywordCondition(condition.getLeft(),
                                condition.getRightList().get(i).getValue(), false,
                                distance, sb);
                        node.setGeo(geo);
                    }
                    sb.append(")");
                }
                break;
            case LE:
                sb.append(condition.getLeft().getColumnName()).append(":[* TO ")
                        .append(condition.getRightOne().getValue()).append("]");
                break;
            case LT:
                sb.append(condition.getLeft().getColumnName()).append(":{* TO ")
                        .append(condition.getRightOne().getValue()).append("}");
                break;
            case LIKE:
                String content = String.valueOf(condition.getRightOne().getValue());
                content = content.replace("%", "");
                boolean geo = buildKeywordCondition(condition.getLeft(), content, true, distance, sb);
                node.setGeo(geo);
                break;
            case NOT_LIKE:
                sb.append("(*:* !").append(condition.getLeft().getColumnName()).append(":")
                        .append(condition.getRightOne().getValue()).append(")");
                break;
            case NE:
                if (condition.getRightList().size() == 1) {
                    {
                        sb.append("(*:* !").append(condition.getLeft().getColumnName()).append(":")
                                .append(condition.getRightOne().getValue()).append(")");
                    }
                } else {
                    //加一层过滤，字段如果没有被indexed,就不能作条件查询,即使查询solr也会报错
                    if (condition.getLeft().getMeta().isNotIndexed()) {
                        throw new SqlParseException("column<" + condition.getLeft().getColumnName() + "> is not indexed,can not <in> more than one value");
                    }
                    sb.append("(*:* ").append(condition.getLeft().getColumnName()).append(":(* ");

                    for (int i = 0; i < condition.getRightList().size(); i++) {
                        sb.append(SOLR_NOT).append(condition.getRightList().get(i).getValue());
                    }

                    sb.append("))");
                }
                break;
            case IS:
                //加一层过滤，字段如果没有被indexed,就不能作条件查询,即使查询solr也会报错
                if (condition.getLeft().getMeta().isNotIndexed()) {
                    throw new SqlParseException("column<" + condition.getLeft().getColumnName() + "> is not indexed,sql <is null> would be error in converted solr grammer");
                }
                sb.append("(*:* !").append(condition.getLeft().getColumnName()).append(":*)");
                break;
            case IS_NOT:
                //加一层过滤，字段如果没有被indexed,就不能作条件查询,即使查询solr也会报错
                if (condition.getLeft().getMeta().isNotIndexed()) {
                    throw new SqlParseException("column<" + condition.getLeft().getColumnName() + "> is not indexed,sql <is not null> would be error in converted solr grammer");
                }
                sb.append("(*:* ").append(condition.getLeft().getColumnName()).append(":*)");
                break;
        }
        node.setSolrExpression(sb.toString());
        return node;
    }

    /**
     * 构建关键字查询
     *
     * @param columnNode
     * @param value
     * @param join
     * @param distance
     * @param sb
     */
    private static boolean buildKeywordCondition(ColumnNode columnNode, Object value, boolean join, Double distance,
                                                 StringBuilder sb) {

        boolean geoFlag = false;

        String content = String.valueOf(value);

        FieldMeta meta = columnNode.getMeta();

        switch (meta.getType()) {
            case INT:
            case LONG:
            case FLOAT:
            case DOUBLE:
                if (content.startsWith("-")) {
                    sb.append(columnNode.getColumnName() + ":\"" + content + "\"");
                } else {
                    sb.append(columnNode.getColumnName() + ":" + content);
                }
                break;
            case STRING:
                if (StringUtils.isBlank(content)) {
                    sb.append(columnNode.getColumnName() + ":\"\"");
                }
//                else if (content.toLowerCase().contains(SolrConstant.IS_NULL)) {
//                    sb.append("!").append(columnNode.getColumnName() + ":*");
//                }
                else {
                    sb.append(columnNode.getColumnName() + ":" + content);
                }
                break;
            case TEXT_LIKE:
                sb.append(columnNode.getColumnName() + ":\"" + content + "\"");
                break;
            case MY_IK:
                if (join) {
                    List<String> ikValue = IKSegmentSupport.segmentList(content, true);
                    if (CollectionUtils.isEmpty(ikValue)) {
                        ikValue.add(content);
                    }
                    sb.append(columnNode.getColumnName()).append(":(").append(StringUtils.join(ikValue, SOLR_OR))
                            .append(")");
                } else {
                    String ikValue = IKSegmentSupport.segment(content, true);
                    if (StringUtils.isEmpty(ikValue)) {
                        ikValue = content;
                    }
                    sb.append(columnNode.getColumnName() + ":\"" + ikValue + "\"");

                }
                break;
            case GEO:
                if (distance == null) {
                    throw new SqlParseException("GEO sql query must contains d condition, please check");
                }

                // 解析location，坑死本宝宝了
                String geoLocation = null;
                if (content.indexOf(",") == -1) {
                    try {
                        String[] arr = content.split("\\s+");
                        geoLocation = arr[1] + "," + arr[0];
                    } catch (Exception e) {
                        throw new SqlParseException("invalid GEO location: [" + content + "], please check");
                    }
                } else {
                    geoLocation = content;
                }

                sb.append("{!geofilt pt=").append(geoLocation).append(" sfield=").append(columnNode.getColumnName())
                        .append(" d=").append(distance).append(" score=distance}");

                geoFlag = true;
                break;
            default:
                sb.append(columnNode.getColumnName() + ":" + content);
                break;
        }
        return geoFlag;
    }
}
